package com.hioove.core.aop;

import com.hioove.core.aop.model.InterceptorContext;
import com.hioove.core.model.base.config.MethodSupport;
import com.hioove.core.reflect.AopProxy;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;

/**
 * 动态代理
 */
public class AopCglibProxy extends AopProxy implements MethodInterceptor {
    static Logger log = LoggerFactory.getLogger(AopCglibProxy.class);
    // 用于aop拦截器中间变量
    final static ThreadLocal<InterceptorContext<?>> INTERCEPTOR_CONTEXT = new ThreadLocal<>();
    static Enhancer enhancer = new Enhancer();

    private List<MethodSupport> pointcut;
    private List<MethodSupport> before;
    private List<MethodSupport> after;
    private List<MethodSupport> afterReturning;
    private List<MethodSupport> afterThrowing;
    private List<MethodSupport> around;

    @Override
    public <T> T create(Class<T> clazz) {
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
        return (T) enhancer.create();
    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        //todo  条件过滤
        process(getPointcut());
        process(getBefore());
        process(getAround());
        // 代理方法执行
        Object proxyResult = proxy.invokeSuper(obj, args);
        process(getAfter());
        // 清理中间变量
        remove();
        return proxyResult;
    }

    private void process(List<MethodSupport> interceptMission) {
        Optional.ofNullable(interceptMission).orElse(Collections.emptyList()).stream().sorted(Comparator.comparing((MethodSupport methodSupport) -> methodSupport.getClassSupport().getInstance().hashCode())).forEach(methodSupport -> {
            Object[] param = methodSupport.getMethod().getParameterCount() == 0 ? new Object[]{} : new Object[]{Optional.ofNullable(INTERCEPTOR_CONTEXT.get()).orElse(new InterceptorContext<>())};
            try {
                Object invoke = methodSupport.getMethod().invoke(methodSupport.getClassSupport().getInstance(), param);
                Stream.of(param.length).filter(integer -> integer != 0).forEach(integer -> INTERCEPTOR_CONTEXT.set((InterceptorContext<?>) invoke));
            } catch (Exception e) {
                log.error("Proxy object exception!!!", e);
            }

        });
    }

    @Override
    public AopProxy setPointcut(List<MethodSupport> pointCut) {
        this.pointcut = pointCut;
        return this;
    }

    @Override
    public AopProxy setBefore(List<MethodSupport> before) {
        this.before = before;
        return this;
    }

    @Override
    public AopProxy setAfter(List<MethodSupport> after) {
        this.after = after;
        return this;
    }

    @Override
    public AopProxy setAfterReturning(List<MethodSupport> afterReturning) {
        this.afterReturning = afterReturning;
        return this;
    }

    @Override
    public AopProxy setAfterThrowing(List<MethodSupport> afterThrowing) {
        this.afterThrowing = afterThrowing;
        return this;
    }

    @Override
    public AopProxy setAround(List<MethodSupport> around) {
        this.around = around;
        return this;
    }

    @Override
    public void init() {

    }

    @Override
    public void destroy() {

    }

    private List<MethodSupport> getPointcut() {
        return pointcut;
    }

    private List<MethodSupport> getBefore() {
        return before;
    }

    private List<MethodSupport> getAfter() {
        return after;
    }

    public static void remove() {
        INTERCEPTOR_CONTEXT.remove();
    }

    public List<MethodSupport> getAfterReturning() {
        return afterReturning;
    }

    public List<MethodSupport> getAfterThrowing() {
        return afterThrowing;
    }

    public List<MethodSupport> getAround() {
        return around;
    }
}
